//=============================================================================
// Game.cpp by Richie Hawley
//
// Main Game Loop 
//
// Controls:
//		'A'/'D'/'W'/'S' - Move
//      Hold down left mouse button and move mouse to rotate.
//
//=============================================================================

#include "Game Engine\Header\Camera.h"
#include "Game Engine\Header\Framework.h"
#include "Game Engine\Header\Light.h"
#include "Game Engine\Header\Player.h"
#include "Game Engine\Header\RigidBody.h"
#include "Game Engine\Header\Terrain.h"
#include "Game Engine\Header\Vehicle.h"
#include "Game Engine\Header\Cessna.h"
#include "Game Engine\Header\Car.h"
#include "Source\Header\DrawableTex2D.h"
#include "Source\Header\Sky.h"

#include <sstream>

#define KEYPRESSED( key )	( ( ( GetAsyncKeyState( key ) & 0x8000 ) ) ? 1 : 0 )

wostream& operator<<(wostream& os, D3DXVECTOR3& v)
{
	os << "X: " << v.x << " Y: " << v.y << " Z: " << v.z;
	return os;
}

class Game : public Framework
{
public:
	~Game();

	Game( HINSTANCE hInstance );

	void Init();
	void OnResize();
	void UpdateScene( float dt );
	void DrawScene(); 

	LRESULT MsgProc( UINT msg, WPARAM wParam, LPARAM lParam );

	void ClientResetScene();

private:
	POINT mOldMousePos;

	Body* mPlayer;

	std::vector<Vehicle*> mCar;

	std::vector<Airplane*> mPlane;

	std::vector<Body*> mObject;

	std::vector<Body*> mStatic;

	Sky mSky;

	DrawableTex2D mShadowMap;

	Light mParallelLight;

	D3DXMATRIX mLightView;
	D3DXMATRIX mLightVolume;

	ID3D10ShaderResourceView* mEnvMapRV;

	bool mSpacePushed;
	float mAngle;

	D3DXVECTOR3 mLightPos;
};

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE prevInstance,
				   PSTR cmdLine, int showCmd )
{
	// Enable run-time memory check for debug builds.
#if defined(DEBUG) | defined(_DEBUG)
	_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
#endif


	Game theApp( hInstance );
	
	theApp.Init();

	return theApp.Run();
}

Game::Game( HINSTANCE hInstance )
: Framework( hInstance ), mEnvMapRV(0), mSpacePushed(false), mAngle( 0.0f ),
mLightPos( D3DXVECTOR3( 0.0f, 0.0f, 0.0f ) )
{
}

Game::~Game()
{
	if( md3dDevice )
		md3dDevice->ClearState();

	fx::DestroyAll();
	InputLayout::DestroyAll();
}

static bool CustomMaterialCombinerCallback( btManifoldPoint& cp, const btCollisionObject* colObj0,
	int partId0, int index0, const btCollisionObject* colObj1, int partId1, int index1 )
{
	btVector3 vel;
	bool objectsSwitched = false;
	if( colObj0->isStaticObject() )
	{
		vel = colObj0->getConveyorVelocity();
		objectsSwitched = true;
	}
	else
	{
		vel = colObj1->getConveyorVelocity();
	}

	if( ( ( vel.x() == 0.0f || vel.x() < -10000.0f ) ) &&
		( ( vel.y() == 0.0f || vel.y() < -10000.0f ) ) &&
		( ( vel.z() == 0.0f || vel.z() < -10000.0f ) ) )
	{
		return false;
	}

	btScalar friction = colObj0->getFriction() * colObj1->getFriction();
	btScalar rest = colObj0->getRestitution() * colObj1->getRestitution();

	cp.m_lateralFrictionInitialized = true;

	///choose a target velocity in the friction dir1 direction, for a conveyor belt effect
	cp.m_lateralFrictionDir1.setValue( vel.x(), vel.y(), vel.z() );
	cp.m_lateralFrictionDir1.normalize();

	cp.m_velocity1 = vel;

	float velLength = D3DXVec3Length( &BT2DX_VECTOR3( vel ) );
	///optionally downscale the friction direction 2 for lower (anisotropic) friction (rather than a unit vector)
	btScalar downscale = 1.0f;
	cp.m_lateralFrictionDir2 = downscale*cp.m_lateralFrictionDir1.cross( cp.m_normalWorldOnB );
	//cp.m_contactCFM2 = 0.1;
	if( objectsSwitched )
		cp.m_contactMotion1 = -velLength * 260.0f;
	else
		cp.m_contactMotion1 = velLength * 260.0f;
	cp.m_combinedFriction = friction;
	cp.m_combinedRestitution = rest;
	return true;
}

void Game::Init()
{
	gContactAddedCallback = CustomMaterialCombinerCallback;

	Framework::Init();

	mEnvMapRV = GetTextureMgr().createCubeTex( L"Textures/grassenvmap1024.dds" );
	mSky.init( md3dDevice, mEnvMapRV, 5000.0f );

	mShadowMap.init( md3dDevice, 2048, 2048, false, DXGI_FORMAT_UNKNOWN );

	GetCamera().Position() = D3DXVECTOR3( 0.0f, 10.8f, -50.0f );

	std::vector<std::wstring> baseMeshes;
	baseMeshes.push_back( L"M3D/Ground2.m3d" );
	baseMeshes.push_back( L"M3D/Wall.m3d" );

	std::vector<std::wstring> loadMeshes;
	loadMeshes.push_back( L"M3D/LoadA.m3d" );
	loadMeshes.push_back( L"M3D/LoadB.m3d" );
	loadMeshes.push_back( L"M3D/LoadB.m3d" );

	mStatic.push_back( new RigidBody() );
	mStatic[0]->Init( md3dDevice, mDynamicsWorld, baseMeshes, fx::MeshFX, mEnvMapRV, 0.0f );
	mStatic[0]->SetPosition( D3DXVECTOR3( 0.0f, -10.0f, 100.0f ) );
	mStatic[0]->SetRotation( RigidBody::Z, mAngle, false );
	mStatic[0]->SetCompoundPosition( 0, 1, D3DXVECTOR3( -100.0f, 100.0f, 0.0f ),
		D3DXVECTOR3( 100.0f, 100.0f, 0.0f ) );
	mStatic[0]->SetBounciness( 1.0f );
	//mStatic[0]->SetRotation( Body::X, 3.0f );
	//mStatic[0]->SetRotation( Body::Z, 20, false );

	//mStatic.push_back( new RigidBody() );
	//mStatic[1]->Init( md3dDevice, mDynamicsWorld, L"M3D/Step.m3d", fx::MeshFX, mEnvMapRV, 0.0f );
	//mStatic[1]->SetPosition( D3DXVECTOR3( 10.0f, -10.0f + ( mStatic[0]->GetHeight(0) / 2 ) +
	//	mStatic[1]->GetHeight() / 2, 0.0f ) );

	//mObject.push_back( new RigidBody() );
	//mObject[0]->Init( md3dDevice, mDynamicsWorld, loadMeshes, fx::MeshFX, mEnvMapRV, 100.0f );
	//mObject[0]->SetPosition( D3DXVECTOR3( -10.0f, -5.0f, 10.0f ) );
	//mObject[0]->SetCompoundPosition( 0, 1, D3DXVECTOR3( 100.0f, 0.0f, 0.0f ),
	//	D3DXVECTOR3( 0.0f, 0.0f, 0.0f ) );
	//mObject[0]->SetCompoundPosition( 0, 2, D3DXVECTOR3( -100.0f, 0.0f, 0.0f ),
	//	D3DXVECTOR3( 0.0f, 0.0f, 0.0f ) );
	//mObject[0]->SetGravity( D3DXVECTOR3( 0.0f, 0.0f, 0.0f ) );

	//mObject.push_back( new RigidBody() );
	//mObject[1]->Init( md3dDevice, mDynamicsWorld, L"M3D/Sphere.m3d", fx::MeshFX, mEnvMapRV, 1.0f, RigidBody::Sph );
	//mObject[1]->SetPosition( D3DXVECTOR3( 10.0f, 10.0f, 10.0f ) );
	//mObject[1]->SetBounciness( 1.0f );

	//mObject.push_back( new RigidBody() );
	//mObject[2]->Init( md3dDevice, mDynamicsWorld, L"M3D/Cup.m3d", fx::MeshFX, mEnvMapRV, 100.0f, RigidBody::TriMesh );
	//mObject[2]->SetPosition( D3DXVECTOR3( 0.0f, 10.0f, 15.0f ) );
	//mObject[2]->SetRotation( RigidBody::Z, 180 );

	//mCar.push_back( new Car() );
	//mCar[0]->Init( md3dDevice, mDynamicsWorld, L"M3D/Chassis.m3d", L"M3D/Wheel.m3d", fx::MeshFX, mEnvMapRV, 400.0f );
	//mCar[0]->SetForkLift( L"M3D/Lift.m3d", L"M3D/ForkB.m3d", L"M3D/ForkA.m3d", L"M3D/ChassisConnect.m3d" );
	//mCar[0]->SetPosition( D3DXVECTOR3( 0.0f, 0.0f, 5.0f ) );
	////mCar[0]->SetGravity( D3DXVECTOR3( 0.0f, 0.0f, 0.0f ) );
	////mVehicle[0]->SetRotation( Vehicle::Y, 90.0f, false );

	mPlane.push_back( new Cessna( 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f ) );
	mPlane[0]->Init( md3dDevice, mDynamicsWorld, L"M3D/Plane.m3d", L"", L"M3D/Ground2.m3d", fx::MeshFX, mEnvMapRV, 400.0f );
	mPlane[0]->SetPosition( D3DXVECTOR3( 0.0f, mStatic[0]->GetPosition().y + ( mStatic[0]->GetHeight() / 2.0f ) +
		( mPlane[0]->GetHeight() / 2.0f ), 0.0f ) );
	mPlane[0]->SetGroundPosition( mStatic[0]->GetPosition() );
	mPlane[0]->SetGroundRotation( mStatic[0]->GetRotation() );
	mPlane[0]->SetGravity( D3DXVECTOR3( 0.0f, 0.0f, 0.0f ) );
	//mVehicle[0]->SetRotation( Vehicle::Y, 90.0f, false );

	//// Needed once to enable ghost objects inside Bullet
	//mPlayer = new Player();
	//mPlayer->Init( md3dDevice, mDynamicsWorld, L"M3D/Sphere.m3d", fx::MeshFX, mEnvMapRV, 100.0f, Player::Sph );
	//mPlayer->SetPosition( D3DXVECTOR3( 0.0f, 0.0f, -5.0f ) );
	//mPlayer->SetBounciness( 1.0f );
	////mPlayer->SetPlayerGravity( 50.0f );

	//mDynamicsWorld->addCollisionObject( mPlayer->GetGhostObject(), btBroadphaseProxy::SensorTrigger |
	//	btBroadphaseProxy::CharacterFilter, btBroadphaseProxy::AllFilter & ~btBroadphaseProxy::SensorTrigger );

	//mDynamicsWorld->addAction( mPlayer->GetCharacter() );

	// direction updated at runtime
	//mParallelLight.dir      = D3DXVECTOR3(0.0f, -0.50f, 0.50f);
	mParallelLight.ambient  = D3DXCOLOR(0.3f, 0.3f, 0.3f, 1.0f);
	mParallelLight.diffuse  = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);
	mParallelLight.specular = D3DXCOLOR(0.5f, 0.5f, 0.5f, 1.0f);

	D3DXMatrixOrthoLH(&mLightVolume, 200.0f, 200.0f, 1.0f, 200.0f);
}

void Game::OnResize()
{
	Framework::OnResize();

	float aspect = (float)mClientWidth/mClientHeight;
	GetCamera().SetLens( 0.25f * PI, aspect, 0.5f, 1000.0f );
}

void Game::UpdateScene( float dt )
{
	Framework::UpdateScene( dt );

	// Update angles based on input to orbit camera around scene.
	if( GetAsyncKeyState('A') & 0x8000 )	GetCamera().Strafe( -20.0f * dt );
	if( GetAsyncKeyState('D') & 0x8000 )	GetCamera().Strafe( +20.0f * dt );
	if( GetAsyncKeyState('W') & 0x8000 )	GetCamera().Walk( +20.0f * dt );
	if( GetAsyncKeyState('S') & 0x8000 )	GetCamera().Walk( -20.0f * dt );
	
	if( GetAsyncKeyState( VK_SPACE ) & 0x8000 && !mSpacePushed )
	{
		mSpacePushed = true;
		for( UINT i = 0; i < mCar.size(); i ++ )
			mCar[i]->ClientResetScene();
		ClientResetScene();
	}
	if( 0 == ( KEYPRESSED( VK_SPACE ) ) )
		mSpacePushed = false;

	float z = mPlane[0]->GetRotation().z;

	GetCamera().RebuildView();

	//mPlayer->Move( dt * 2.5f );

	for( UINT i = 0; i < mCar.size(); i ++ )
	{
		mCar[i]->Move( dt );
	}

	float oldDX = mPlane[0]->GetRotation().x;
	float oldDY = mPlane[0]->GetRotation().y;
	float oldDZ = mPlane[0]->GetRotation().z;

	for( UINT i = 0; i < mPlane.size(); i ++ )
	{
		mPlane[i]->Move( dt );
	}

	float dx = mPlane[0]->GetRotation().x - oldDX;
	float dy = mPlane[0]->GetRotation().y - oldDY;
	float dz = mPlane[0]->GetRotation().z - oldDZ;

		//GetCamera().YawPitchRoll( D3DXVECTOR3( dx, dy, dz ) );
		GetCamera().Position() = D3DXVECTOR3( mPlane[0]->GetPosition().x - ( mPlane[0]->GetLook().x * 10.0f ),
			mPlane[0]->GetPosition().y + 1.0f, mPlane[0]->GetPosition().z - ( mPlane[0]->GetLook().z * 10.0f ) );
		GetCamera().GetCameraLook() = mPlane[0]->GetLook();
	if( GetAsyncKeyState('C') & 0x8000 )
	{
	}
	if( GetAsyncKeyState('X') & 0x8000 )
	{
		GetCamera().YawPitchRoll( D3DXVECTOR3( dx, dy, dz ) );
		GetCamera().Position() = D3DXVECTOR3( mPlane[0]->GetPosition().x,
			mPlane[0]->GetPosition().y, mPlane[0]->GetPosition().z + 1.0f );
	}

	// Animate light and keep shadow in sync.
	D3DXVECTOR3 lightPos;
	lightPos.x = 100.0f * cosf( 0.25f ); //* mTimer.getGameTime() );
	lightPos.y = 100.0f;
	lightPos.z = 100.0f * sinf(0.25f ); //* mTimer.getGameTime() );

	mLightPos.x = mPlane[0]->GetPosition().x;
	mLightPos.y = ( 100.0f ) + mPlane[0]->GetPosition().y;
	mLightPos.z = mPlane[0]->GetPosition().z;

	D3DXMatrixLookAtLH( &mLightView, &mLightPos,
		&mPlane[0]->GetPosition(), &D3DXVECTOR3( 0.0f, 1.0f, 0.0f ) );
		//&D3DXVECTOR3( 0.0f, 1.0f, 0.0f ), &D3DXVECTOR3( 0.0f, 1.0f, 0.0f ) );

	D3DXVECTOR3 lightDirection = -lightPos;
	D3DXVec3Normalize( &mParallelLight.dir, &lightDirection );

	mAngle += dt * 10;
	if( mAngle >= 360.0f )
		mAngle = 0.0f;

	//mStatic[0]->SetCompoundVelocity( 1, D3DXVECTOR3( dt * 10.0f, 0.0f, 0.0f ) );
	//mStatic[0]->SetRotation( Object::Z, mAngle, false );
	//mStatic[0]->SetVelocity( D3DXVECTOR3( dt * 0.2f, 0.0f, 0.0f ) );
	//mStatic[0]->SetPosition( mStatic[0]->GetPosition() + mStatic[0]->GetVelocity() );
}

void Game::DrawScene()
{
	Framework::DrawScene();

	//////////////////// Uncomment this section to use shadows /////////////////////
	//////// Make sure to set useShadows to true in the mesh.Init function /////////

	//
	// Draw scene to the shadow map.
	//

	mShadowMap.begin();

	D3D10_TECHNIQUE_DESC techDesc;
	mBuildShadowMapTech->GetDesc( &techDesc );

	for(UINT i = 0; i < techDesc.Passes; ++i)
	{
		ID3D10EffectPass* pass = mBuildShadowMapTech->GetPassByIndex(i);

		for( UINT i = 0; i < mStatic.size(); i ++ )
		{
			mStatic[i]->DrawToShadowMap( mfxBuildShadowMapLightWVPVar, mfxBuildShadowMapDiffuseMapVar, pass, mLightView * mLightVolume );
		}

		for( UINT i = 0; i < mObject.size(); i ++ )
		{
			//mObject[i]->DrawToShadowMap( mfxBuildShadowMapLightWVPVar, mfxBuildShadowMapDiffuseMapVar, pass, mLightView * mLightVolume );
		}

		for( UINT i = 0; i < mCar.size(); i ++ )
		{
			//mCar[i]->DrawToShadowMap( mfxBuildShadowMapLightWVPVar, mfxBuildShadowMapDiffuseMapVar, pass, mLightView * mLightVolume );
		}

		for( UINT i = 0; i < mPlane.size(); i ++ )
		{
			mPlane[i]->DrawToShadowMap( mfxBuildShadowMapLightWVPVar, mfxBuildShadowMapDiffuseMapVar, pass, mLightView * mLightVolume );
		}

		//mPlayer->DrawToShadowMap( mfxBuildShadowMapLightWVPVar, mfxBuildShadowMapDiffuseMapVar, pass, mLightView * mLightVolume );
	}

	mShadowMap.end();

	// restore rendering to backbuffer
	ResetOMTargetsAndViewport();

	mStatic[0]->SetShadowMap( mShadowMap.depthMap() );

	////////////////////////////////////////////////////////////////////////////////
	mStatic[0]->SetLight( mParallelLight );
	mStatic[0]->SetEyePos( GetCamera().Position() );
	mStatic[0]->EnableCubeMap( false );	

	D3DXMATRIX I;
	D3DXMatrixIdentity( &I );
	mfxTexMtxVar->SetMatrix( ( float* ) I );

	for( UINT i = 0; i < mStatic.size(); i ++ )
	{
		mStatic[i]->Draw( mLightView * mLightVolume );
	}

	for( UINT j = 0; j < mObject.size(); j ++ )
	{
		//mObject[j]->Draw( mLightView * mLightVolume );
	}

	for( UINT k = 0; k < mCar.size(); k ++ )
	{
		//mCar[k]->Draw( mLightView * mLightVolume );
	}

	for( UINT k = 0; k < mPlane.size(); k ++ )
	{
		mPlane[k]->Draw( mLightView * mLightVolume );
	}

	//mPlayer->Draw( mLightView * mLightVolume );

	/////////////////////// Uncomment this when using shadows //////////////////////

	mStatic[0]->UnbindShadowMap();

	////////////////////////////////////////////////////////////////////////////////

	// Draw sky last to save fill rate.
	mSky.draw();

	// We specify DT_NOCLIP, so we do not care about width/height of the rect.
	RECT R = { 5, 5, 0, 0 };
	md3dDevice->RSSetState(0);
	mFont->DrawText( 0, mFrameStats.c_str(), -1, &R, DT_NOCLIP, WHITE );

	// We specify DT_NOCLIP, so we do not care about width/height of the rect.
	RECT R2 = { 5, 100, 0, 0 };
	std::wostringstream pPos;
	pPos.precision(6);
	pPos << L"Throttle: " << mPlane[0]->mThrottle << endl <<
		L"Heading Angle: " << mPlane[0]->mHeadingAngle << endl <<
		L"Climb Angle: " << mPlane[0]->mClimbAngle << endl <<
		L"Airspeed: " << mPlane[0]->mAirSpeed << endl <<
		L"Climb Rate: " << mPlane[0]->mClimbRate << endl <<
		L"Altitude: " << mPlane[0]->mAltitude;
	std::wstring playerPos = pPos.str();
	mFont->DrawText( 0, playerPos.c_str(), -1, &R2, DT_NOCLIP, WHITE );

	//// We specify DT_NOCLIP, so we do not care about width/height of the rect.
	//RECT R3 = { 400, 25, 0, 0 };
	//std::wostringstream ePos;
	//ePos.precision(6);
	//ePos << L"Player Vel: " << mPlayer->GetCharacter()->getVelocity().z(); //BT2DX_VECTOR3( mPlayer->GetCharacter()->getVelocity() );
	//std::wstring envPos = ePos.str();
	//mFont->DrawText( 0, envPos.c_str(), -1, &R3, DT_NOCLIP, WHITE );

	//// We specify DT_NOCLIP, so we do not care about width/height of the rect.
	//RECT R4 = { 600, 5, 0, 0 };
	//std::wostringstream pVel;
	//pVel.precision(6);
	//pVel << L"Player Vel: " << mPlayer->GetGhostObject()->getWorldTransform().getOrigin().z() - mPlayer->GetCharacter()->m_currentPosition.z();
	//std::wstring playerVel = pVel.str();
	//mFont->DrawText( 0, playerVel.c_str(), -1, &R4, DT_NOCLIP, WHITE );

	mSwapChain->Present(0, 0);
}

LRESULT Game::MsgProc( UINT msg, WPARAM wParam, LPARAM lParam )
{
	POINT mousePos;
	int dx = 0;
	int dy = 0;
	switch(msg)
	{
	case WM_LBUTTONDOWN:
		if( wParam & MK_LBUTTON )
		{
			SetCapture(mMainWnd);

			mOldMousePos.x = LOWORD( lParam );
			mOldMousePos.y = HIWORD( lParam );
		}
		return 0;

	case WM_LBUTTONUP:
		ReleaseCapture();
		return 0;

	case WM_MOUSEMOVE:
		if( wParam & MK_LBUTTON )
		{
			mousePos.x = (int)LOWORD( lParam ); 
			mousePos.y = (int)HIWORD( lParam ); 

			dx = mousePos.x - mOldMousePos.x;
			dy = mousePos.y - mOldMousePos.y;

			GetCamera().Pitch( dy * 0.0087266f );
			GetCamera().RotateY( dx * 0.0087266f );
			
			mOldMousePos = mousePos;
		}
		return 0;
	}

	return Framework::MsgProc( msg, wParam, lParam );
}

void Game::ClientResetScene()
{
	for( UINT i = 0; i < mObject.size(); i ++ )
		mObject[i]->SetPosition( D3DXVECTOR3( 0.0f, 0.0f, 10.0f + ( i * 5.0f ) ) );

	for( UINT i = 0; i < mStatic.size(); i ++ )
		mStatic[i]->SetPosition( D3DXVECTOR3( 0.0f, -10.0f, 0.0f ) );

	for( UINT i = 0; i < mCar.size(); i ++ )
		mCar[i]->SetPosition( D3DXVECTOR3( 0.0f, 0.0f, 5.0f ) );

	//for( UINT i = 0; i < mCar.size(); i ++ )
	//	mPlane[i]->SetPosition( D3DXVECTOR3( 0.0f, 5.0f, 5.0f ) );

	//mPlayer->SetPosition( D3DXVECTOR3( 0.0f, 0.0f, -5.0f ) );
}